package core

import (
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"codeberg.org/vnpower/pixivfe/v2/audit"
	"codeberg.org/vnpower/pixivfe/v2/server/session"
	"github.com/goccy/go-json"
)

type Novel struct {
	Bookmarks      int       `json:"bookmarkCount"`
	CommentCount   int       `json:"commentCount"`
	MarkerCount    int       `json:"markerCount"`
	CreateDate     time.Time `json:"createDate"`
	UploadDate     time.Time `json:"uploadDate"`
	Description    string    `json:"description"`
	ID             string    `json:"id"`
	Title          string    `json:"title"`
	Likes          int       `json:"likeCount"`
	Pages          int       `json:"pageCount"`
	UserID         string    `json:"userId"`
	UserName       string    `json:"userName"`
	Views          int       `json:"viewCount"`
	IsOriginal     bool      `json:"isOriginal"`
	IsBungei       bool      `json:"isBungei"`
	XRestrict      int       `json:"xRestrict"`
	Restrict       int       `json:"restrict"`
	Content        string    `json:"content"`
	CoverURL       string    `json:"coverUrl"`
	IsBookmarkable bool      `json:"isBookmarkable"`
	BookmarkData   any       `json:"bookmarkData"`
	LikeData       bool      `json:"likeData"`
	PollData       any       `json:"pollData"`
	Marker         any       `json:"marker"`
	Tags           struct {
		AuthorID string `json:"authorId"`
		IsLocked bool   `json:"isLocked"`
		Tags     []struct {
			Name string `json:"tag"`
		} `json:"tags"`
		Writable bool `json:"writable"`
	} `json:"tags"`
	SeriesNavData struct {
		SeriesType    string `json:"seriesType"`
		SeriesID      int    `json:"seriesId"`
		Title         string `json:"title"`
		IsConcluded   bool   `json:"isConcluded"`
		IsReplaceable bool   `json:"isReplaceable"`
		IsWatched     bool   `json:"isWatched"`
		IsNotifying   bool   `json:"isNotifying"`
		Order         int    `json:"order"`
		Next          struct {
			Title     string `json:"title"`
			Order     int    `json:"order"`
			ID        string `json:"id"`
			Available bool   `json:"available"`
		} `json:"next"`
		Prev struct {
			Title     string `json:"title"`
			Order     int    `json:"order"`
			ID        string `json:"id"`
			Available bool   `json:"available"`
		} `json:"prev"`
	} `json:"seriesNavData"`
	HasGlossary bool `json:"hasGlossary"`
	IsUnlisted  bool `json:"isUnlisted"`
	// seen values: zh-cn, ja
	Language       string `json:"language"`
	CommentOff     int    `json:"commentOff"`
	CharacterCount int    `json:"characterCount"`
	WordCount      int    `json:"wordCount"`
	UseWordCount   bool   `json:"useWordCount"`
	ReadingTime    int    `json:"readingTime"`
	AiType         int    `json:"aiType"`
	Genre          string `json:"genre"`
	Settings       struct {
		ViewMode int `json:"viewMode"`
		// ...
	} `json:"suggestedSettings"`
	TextEmbeddedImages map[string]struct {
		NovelImageId string `json:"novelImageId"`
		SanityLevel  string `json:"sl"`
		Urls         struct {
			Two40Mw     string `json:"240mw"`
			Four80Mw    string `json:"480mw"`
			One200X1200 string `json:"1200x1200"`
			One28X128   string `json:"128x128"`
			Original    string `json:"original"`
		} `json:"urls"`
	} `json:"textEmbeddedImages"`
	CommentsList []Comment
	UserNovels   map[string]*NovelBrief `json:"userNovels"`
}

type NovelBrief struct {
	ID             string    `json:"id"`
	Title          string    `json:"title"`
	XRestrict      int       `json:"xRestrict"`
	Restrict       int       `json:"restrict"`
	CoverURL       string    `json:"url"`
	Tags           []string  `json:"tags"`
	UserID         string    `json:"userId"`
	UserName       string    `json:"userName"`
	UserAvatar     string    `json:"profileImageUrl"`
	TextCount      int       `json:"textCount"`
	WordCount      int       `json:"wordCount"`
	ReadingTime    int       `json:"readingTime"`
	Description    string    `json:"description"`
	IsBookmarkable bool      `json:"isBookmarkable"`
	BookmarkData   any       `json:"bookmarkData"`
	Bookmarks      int       `json:"bookmarkCount"`
	IsOriginal     bool      `json:"isOriginal"`
	CreateDate     time.Time `json:"createDate"`
	UpdateDate     time.Time `json:"updateDate"`
	IsMasked       bool      `json:"isMasked"`
	SeriesID       string    `json:"seriesId"`
	SeriesTitle    string    `json:"seriesTitle"`
	IsUnlisted     bool      `json:"isUnlisted"`
	AiType         int       `json:"aiType"`
	Genre          string    `json:"genre"`
}

// Novel embedded illusts
var (
	re_r  = regexp.MustCompile(`\[pixivimage:\d+(-\d+)?\]`)
	re_d  = regexp.MustCompile(`\d+(-\d+)?`)
	re_t  = regexp.MustCompile(`\"original\":\"(.+?)\"`)
	re_u  = regexp.MustCompile(`\[uploadedimage:(\d+)\]`)
	re_id = regexp.MustCompile(`\d+`)
)

func GetNovelByID(auditor *audit.Auditor, r *http.Request, id string) (Novel, error) {
	var novel Novel

	URL := GetNovelURL(id)

	resp, err := API_GET_UnwrapJson(r.Context(), auditor, URL, "", r.Header)
	if err != nil {
		return novel, err
	}

	resp, err = session.ProxyImageUrl(r, resp)
	if err != nil {
		return novel, err
	}

	err = json.Unmarshal([]byte(resp), &novel)
	if err != nil {
		return novel, err
	}

	// Clean up UserNovels map by removing null entries
	if novel.UserNovels != nil {
		cleanedUserNovels := make(map[string]*NovelBrief)
		for id, novelBrief := range novel.UserNovels {
			if novelBrief != nil {
				cleanedUserNovels[id] = novelBrief
			}
		}
		novel.UserNovels = cleanedUserNovels
	}

	// Initialize view mode with the novel's setting
	viewMode := novel.Settings.ViewMode

	// Check for Cookie_NovelViewMode override
	if cookieViewMode := session.GetCookie(r, session.Cookie_NovelViewMode); cookieViewMode != "" {
		if vmInt, err := strconv.Atoi(cookieViewMode); err == nil {
			viewMode = vmInt
		}
	}

	// Replace [pixivimage:...] tags with actual images
	novel.Content = re_r.ReplaceAllStringFunc(novel.Content, func(s string) string {
		illustid := re_d.FindString(s)

		URL := GetInsertIllustURL(novel.ID, illustid)
		response, err := API_GET_UnwrapJson(r.Context(), auditor, URL, "", r.Header)
		if err != nil {
			return "Cannot insert illust" + illustid
		}

		url := re_t.FindString(response)
		url, err = session.ProxyImageUrl(r, url[11:]) // truncate the "original":
		if err != nil {
			return ""
		}

		// make [pixivimage:illustid-index] jump to anchor
		link := fmt.Sprintf("/artworks/%s", strings.ReplaceAll(illustid, "-", "#"))

		// Styling for horizontal text (default for viewMode 0 and 1)
		imgClass := "img-fluid w-100 w-md-50 rounded drop-shadow"
		wrapperClass := "d-flex justify-content-start w-100 my-3"
		// Styling for vertical text
		if viewMode == 2 {
			imgClass = "img-fluid h-100 w-100 rounded drop-shadow"
			wrapperClass = "d-flex justify-content-start h-100 ms-3 me-3"
		}

		return fmt.Sprintf(`<div class="%s"><a href="%s" target="_blank"><img src=%s alt="%s" class="%s"/></a></div>`, wrapperClass, link, url, s, imgClass)
	})

	// Replace [uploadedimage:...] tags with actual images from TextEmbeddedImages
	novel.Content = re_u.ReplaceAllStringFunc(novel.Content, func(s string) string {
		imageId := re_id.FindString(s)
		if val, ok := novel.TextEmbeddedImages[imageId]; ok {
			// Styling for horizontal text (default for viewMode 0 and 1)
			imgClass := "img-fluid w-100 w-md-50 rounded drop-shadow"
			wrapperClass := "d-flex justify-content-start w-100 my-3"
			// Styling for vertical text
			if viewMode == 2 {
				imgClass = "img-fluid h-100 w-100 rounded drop-shadow"
				wrapperClass = "d-flex justify-content-start h-100 ms-3 me-3"
			}
			return fmt.Sprintf(`<div class="%s"><img src=%s alt="%s" class="%s"/></div>`, wrapperClass, val.Urls.Original, s, imgClass)
		}
		return s
	})

	return novel, nil
}

func GetNovelRelated(auditor *audit.Auditor, r *http.Request, id string) ([]NovelBrief, error) {
	var novels struct {
		List []NovelBrief `json:"novels"`
	}

	// hard-coded value, may change
	URL := GetNovelRelatedURL(id, 180)

	resp, err := API_GET_UnwrapJson(r.Context(), auditor, URL, "", r.Header)
	if err != nil {
		return novels.List, err
	}

	resp, err = session.ProxyImageUrl(r, resp)
	if err != nil {
		return novels.List, err
	}

	err = json.Unmarshal([]byte(resp), &novels)
	if err != nil {
		return novels.List, err
	}

	return novels.List, nil
}
